import java.io.*;
import java.net.*;
import phoneinterface.*;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

public class PhoneProcess {
	
	public enum State {
		IDLE,
		WAITING,
		RINGING,
		OFF_HOOK,
		IN_CALL,
		NO_CALL
	}

	private static LinkedBlockingQueue<Event> eventList;
	
	private static State state;
	private static String phoneIP;
	private static String callIP;
	private static String ext = "";
	
	private static Socket pSocket = null;
	private static PrintWriter out = null;
	private static BufferedReader in = null;
	
	private static PhoneInterface pInterface;
	
	private static long digitTimeout;

	
	static class PhoneThread extends Thread {
		private PhoneInterface pInterface;
		public boolean running = true;
	    public PhoneThread(PhoneInterface p) {
	    	super("PhoneThread");
	    	pInterface = p;
	    }
	    
	    public void run() {
	    	Event e;
	    	while(running) {
	    		
	    		try {
	    			// TODO: Make getEvent thread safe.
	    			e = pInterface.getEvent();
	    			eventList.put(e);
	    		} catch (Exception c) {
	    			System.out.println("Error with phone hardware...");
	    			break;
	    		}
	    		
	        }	    	
	    }
	}

	private static void initializePhone() {
		
		pInterface = new PhoneInterface("TempPhone", IP.getIP(phoneIP), 3001, "commando.student.cs", 5000);
		//pInterface = new PhoneInterface("TempPhone", IP.getIP(phoneIP), 3001, "localhost", 5000);
		
    	//acquire source phone 
    	if(!pInterface.AcquireResource()){
    	    System.out.println("Can't acquire source phone (caller)");
    	    System.exit(-1);
    	}
    	
    	state = State.IDLE;
    	
	}
	
	private static void connectToPhoneServer() throws InterruptedException {
        
		//String serverIP = "129.97.49.167";
        String serverIP = "localhost";
        int serverPort = 3000;
        
        while (true) { //(pSocket == null || !pSocket.isConnected()) {
	        
	        try {
	            pSocket = new Socket(serverIP, serverPort);
	            out = new PrintWriter(pSocket.getOutputStream(), true);
	            in = new BufferedReader(new InputStreamReader(pSocket.getInputStream()));
	        } catch (UnknownHostException e) {
	            System.err.println("Don't know about host: " + serverIP + ".");
	            System.exit(1);
	        } catch (IOException e) {
	            System.err.println("Couldn't get I/O for the connection to: " + serverIP + ".");
	            //System.exit(1);
	        }
	     
	        if (pSocket.isConnected())
	        	break;
	        
	        System.err.println("Retrying connection to PhoneServer in 3 seconds...");
	        Thread.sleep(3000);
	        
        }
        
        // Send phone's name to the server;
        out.println("Name:" + phoneIP);
        
	}

	private static void processEvent() {
		
       	Event e = eventList.poll();
        
    	if (e != null) {
    		//process event
    		
    		if (e.getType() == Event.DIGIT_PRESSED_TYPE) {
    			pInterface.playTone(Tone.DIAL_PULSE1, Cadence.CONSTANT);
    		} else if (e.getType() == Event.DIGIT_RELEASED_TYPE) {
    			if (state == State.OFF_HOOK) {
    				pInterface.playTone(Tone.TONE_OFF, Cadence.CONSTANT);
    				dialDigit(e.getDigit(), out);
    				pInterface.DisplayString(ext, 0, 0);
    			
    			}
    			
    		} else if (e.getType() == Event.OFF_HOOK_TYPE) {
    			pInterface.handsetOn();
    			out.println("OffHook");
    			
    			
    		} else if (e.getType() == Event.ON_HOOK_TYPE) {
    			pInterface.handsetOff();
    			pInterface.playTone(Tone.TONE_OFF, Cadence.CONSTANT);
    			out.println("OnHook");
    			
    			if (state == State.IN_CALL) {
    				pInterface.stopAudioSend(IP.getIP(callIP));
    				pInterface.stopAudioReceive(IP.getIP(callIP));
    			}
    			
    			state = State.IDLE;
    			clearPhoneState();
    			
    		}
    	}
		
	}
	
	private static int processServerMessage() throws IOException {
	
		if (in.ready()) {
			String fromServer = in.readLine();
    		
    		if (fromServer.equals("Bye")) {
    			return -1;
    		}
    		
    		String[] p = fromServer.split(":");
    		if (p[0].equals("HWTest")) {
    			//Return successful hardware test
    			processHWTestMessage();
    			
    		} else if (p[0].equals("DialTone")) {
    			
    			processDialToneMessage(p[1]);
    			
    		} else if (p[0].equals("CallerRinging")) {
    			
    			processCallerRingingMessage();
    			
    		} else if (p[0].equals("CalleeRinging")) {
    			
    			processCalleeRingingMessage();
    			
    		} else if (p[0].equals("EndRinging")) {
    			
    			processEndRingingMessage();
    			
    		} else if (p[0].equals("StartCall")) {
    			
    			processStartCallMessage(p[1]);
    			
    		} else if (p[0].equals("EndCall")) {
    			        			
    			processEndCallMessage();
    			
    		} else if (p[0].equals("CallFailed")) {
    			
    			processCallFailedMessage();
    			
    		} else if (p[0].equals("Reset")) {
    			
    			processResetMessage();
    			
    		}
		}
		
		return 0;
		
	}
	
	
	private static void processHWTestMessage() {
		out.println("HWTestPassed");
	}
	
	private static void processDialToneMessage(String tone) {
		if (tone.equals("0")) {
			pInterface.playTone(Tone.DIAL_TONE, Cadence.CONSTANT);
			state = State.OFF_HOOK;
		} else if (tone.equals("1")) {
			pInterface.playTone(Tone.BUSY, Cadence.CONSTANT);
			state = State.NO_CALL;
		} else if (tone.equals("2")) {
			pInterface.playTone(Tone.TONE1, Cadence.CONSTANT);
			state = State.OFF_HOOK;
		}
	}
	
	private static void processCallerRingingMessage() {
		pInterface.playTone(Tone.RINGING , Cadence.RINGING);
		state = State.WAITING;
	}
	
	private static void processCalleeRingingMessage() {
		pInterface.startRinging();
		state = State.RINGING;
	}
	
	private static void processEndRingingMessage() {
		if (state == State.RINGING) {
			pInterface.stopRinging();
			state = State.IDLE;
		} else if (state == State.WAITING) {
			pInterface.playTone(Tone.TONE_OFF, Cadence.CONSTANT);
			pInterface.playTone(Tone.BUSY, Cadence.CONSTANT);
			state = State.NO_CALL;
		}
	}
	
	private static void processStartCallMessage(String ip) {
		pInterface.stopRinging();
		pInterface.playTone(Tone.TONE_OFF, Cadence.CONSTANT);
		
		callIP = ip;
		pInterface.startAudioSend(IP.getIP(ip));
		pInterface.startAudioReceive(IP.getIP(ip));
		state = State.IN_CALL;
	}
	
	private static void processEndCallMessage() {
		if (state == State.IN_CALL) {
			pInterface.stopAudioSend(IP.getIP(callIP));
			pInterface.stopAudioReceive(IP.getIP(callIP));
		}
		pInterface.playTone(Tone.BUSY, Cadence.CONSTANT);
		state = State.NO_CALL;
	}
	
	private static void processCallFailedMessage() {
		pInterface.playTone(Tone.BUSY, Cadence.CONSTANT);
		pInterface.DisplayString("Call Failed", 1, 0);
		
		state = State.NO_CALL;
	}
	
	private static void processResetMessage() {
		System.err.println("Server is resetting...");
		out.println("ResetState");
		
		clearPhoneState();
		
		if (state == State.IN_CALL) {
			pInterface.stopAudioSend(IP.getIP(callIP));
			pInterface.stopAudioReceive(IP.getIP(callIP));
		}
		
		if (state != State.IDLE && state != State.RINGING) {
			state = State.NO_CALL;
			pInterface.playTone(Tone.BUSY, Cadence.CONSTANT);    				
		} else {
			state = State.IDLE;
		}

		resetPhone();
	}
	
	private static void resetPhone() {
		
		try {
			pSocket.close();
			Thread.sleep(5000);
			connectToPhoneServer();
		} catch (Exception e) {
			System.err.println("Unable to reset phone. Closing PhoneProcess...");
			System.exit(-1);
		}
		
	}
	
	private static void mainLoop() throws IOException, InterruptedException {
		
		BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
		
		while (!pSocket.isClosed()) {
            
			processEvent();
			if (processServerMessage() == -1) {
				break;
			}
			
			checkDigitTimeout();
			
        	if (stdIn.ready()) {
        		String fromUser = stdIn.readLine();
        		if (fromUser.equals("exit"))
        			out.println("Bye");
        			out.flush();
        			break;
        	}
        	
        	
        	Thread.sleep(10);
        	
        }
		
		stdIn.close();		
	}
	
	
    public static void main(String[] args) throws IOException, InterruptedException {

    	eventList = new LinkedBlockingQueue<Event>();
    	
    	phoneIP = args[0];
    	
    	initializePhone();
    	connectToPhoneServer();
        
    	PhoneThread pT = new PhoneThread(pInterface);
    	pT.start();
    	
    	clearPhoneState();
    	
    	mainLoop();
    	
        pT.running = false;
        pT.join();
        
        out.close();
        in.close();
        pSocket.close();
    }
    
    private static void dialDigit(int digit, PrintWriter out) {
    	
    	if (ext.length() < 4) {

        	if (digit == 10) {
        		ext = ext + "*";	
        	} else if (digit == 11) {
        		ext = ext + "#";
        	} else {
        		ext = ext + Integer.toString(digit);
        	}
        	
        	if (ext.length() == 4) {
        		if (ext.substring(0, 1).equals("*") || ext.substring(0,1).equals("#"))
        			ext = ext.substring(1);
        		out.println("Call:" + ext);
        	}
        	
        	digitTimeout = System.currentTimeMillis();
        	
    	}
    	
    }
    
    private static void checkDigitTimeout() {
    	if (ext.length() < 4 && ext.length() > 0 && ((System.currentTimeMillis() - digitTimeout) > 4000)) {
    		ext = "";
    		clearDisplay();
    		pInterface.playTone(Tone.BUSY, Cadence.CONSTANT);
			state = State.NO_CALL;
    	}
    }

    
    private static void clearDisplay() {
    	pInterface.DisplayString("                    ", 0, 0);
    	pInterface.DisplayString("                    ", 1, 0);
    	pInterface.DisplayString("                    ", 2, 0);
    }
    
    private static void clearPhoneState() {
    	
    	ext = "";
    	pInterface.stopRinging();
    	pInterface.playTone(Tone.TONE_OFF, Cadence.CONSTANT);
    	clearDisplay();
    	
    }
    
}